const { cloneDeep, get, set } = require('lodash')
const { findMembers } = require('../util')
const { ParameterException } = require('../http-exception')
const { isFunc, genResult } = require('./tool')
const { Rule, Rules } = require('./rule')

class Validator {
  constructor() {
    this.data = {}
    this.parsed = {}
  }
  // _方法可表示不对外暴露使用，只有当前类内部使用
  /**
   * 获取请求上的所有参数
   */
  _assembleAllParams(ctx) {
    return {
      body: ctx.request.body,
      query: ctx.request.query,
      path: ctx.params, // 这里不知道有没有写错
      header: ctx.request.header,
    }
  }

  get(path, parsed = true) {
    if (parsed) {
      const value = get(this.parsed, path, null)
      if (value == null) {
        const keys = path.split('.')
        const key = last(keys)
        return get(this.parsed.default, key)
      }
      return value
    } else {
      return get(this.data, path) // 获取对象中的对应路径的值
    }
  }

  _findMembersFilter(key) {
    if (/validate([A-Z])\w+/g.test(key)) {
      return true
    }
    // 用数组标识，当前是一个规则
    if (this[key] instanceof Array) {
      this[key].forEach((value) => {
        const isRuleType = value instanceof Rule
        if (!isRuleType) {
          throw new Error('验证数组必须全部为Rule实例')
        }
      })
      return true
    }
    return false
  }
  // 验证入口函数
  async start(ctx, alias = {}) {
    this.alias = alias
    let params = this._assembleAllParams(ctx)
    this.data = cloneDeep(params) // 备份所有的请求参数
    this.parsed = cloneDeep(params)

    //  找到参数名集合
    const memberKeys = findMembers(this, {
      filter: this._findMembersFilter.bind(this),
    })

    const errorMsgs = []
    // const map = new Map(memberKeys)
    //  对每个参数进行检查，所以可以封装成一个函数
    for (let key of memberKeys) {
      const { success, msg } = await this._check(key, alias)
      if (!success) {
        errorMsgs.push(msg)
      }
    }
    if (errorMsgs.length != 0) {
      throw new ParameterException(errorMsgs)
    }
    ctx.v = this
    return this
  }
  // 对每一个key做检查
  async _check(key, alias = {}) {
    let result
    const _isFunc = isFunc(this[key])
    if (_isFunc) {
      try {
        await this[key](this.data)
        result = genResult(true)
      } catch (error) {
        result = genResult(false, error.msg || error.message || '参数错误')
      }
      // 函数验证
    } else {
      // 属性验证, 数组，内有一组Rule
      const rules = this[key]
      const ruleField = new Rules(rules) // 规则
      // 别名替换
      key = alias[key] ? alias[key] : key
      const { value, path } = this._findValue(key) // 值

      result = ruleField.checkRules(value)

      if (result.pass) {
        // 如果参数路径不存在，往往是因为用户传了空值，而又设置了默认值
        if (path.length == 0) {
          set(this.parsed, ['default', key], result.value)
        } else {
          set(this.parsed, path, result.value)
        }
      }
    }
    const msg = `${_isFunc ? '' : key}${result.msg}`
    return {
      msg: result.pass ? null : msg,
      success: result.pass,
    }
  }
  /* 从  query | body | path |  header 中获取目标key val 值*/
  _findValue(key) {
    let res
    const storageList = ['query', 'body', 'path', 'header']
    const findVal = (storageKey) => {
      let val = get(this.data, [storageKey, key])
      if (val) {
        return {
          value: val,
          path: [storageKey, key],
        }
      }
    }
    // 一个参数最多需要遍历4次才能找到在哪
    for (let i = 0; i < storageList.length; i++) {
      const storageKey = storageList[i]
      res = findVal(storageKey)
      if (res) {
        break
      }
    }
    return res || { value: null, path: [] }
  }
}

module.exports = Validator
